package com.study.living.camera;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;

import com.study.living.mediacodec.SurfaceEncoder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@TargetApi(21)
public class Camera2 extends AbsCamera {
    private static final String TAG = "Camera2";

    private final CameraManager mCameraManager;

    private String mCameraId;
    private CameraDevice mCamera;
    private CameraCharacteristics mCameraCharacteristics;
    private CameraCaptureSession mCaptureSession;
    private CaptureRequest.Builder mPreviewRequestBuilder;

    private boolean needImageReaderSurface = false;
    private Surface mImageReaderSurface;

    private final CameraDevice.StateCallback mOpenCameraCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.i(TAG, "CameraDevice onOpened: ");
            mCamera = camera;
            startCaptureSession();
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            mCamera = null;
            Log.i(TAG, "CameraDevice onClosed: ");
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            //其他应用打开相机，抢占相机，导致此相机失联
            Log.i(TAG, "CameraDevice onDisconnected: ");
            mCamera = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.e(TAG, "CameraDevice onError: " + camera.getId() + " (" + error + ")");
            mCamera = null;
        }
    };

    public Camera2(Context context, TexturePreview texturePreview) {
        super(context, texturePreview);
        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        mPreview.setOnSurfaceAvailableListener(new TexturePreview.OnSurfaceAvailableListener() {
            @Override
            public void onSurfaceAvailable() {
                startCaptureSession();
            }

            @Override
            public void onSurfaceDestroyed() {
                stop();
            }
        });
    }

    @Override
    public boolean start() {
        Log.i(TAG, "start: ");
        if (!chooseCameraByFacing()) {
            return false;
        }
        openCamera();
        return true;
    }

    @Override
    public void stop() {
        Log.i(TAG, "stop: ");
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }

        if (mCamera != null) {
            mCamera.close();
            mCamera = null;
        }
    }


    @Override
    public boolean chooseCameraByFacing() {
        Log.d(TAG, "chooseCameraIdByFacing: ");
        try {
            String[] ids = mCameraManager.getCameraIdList();
            if (ids.length == 0) {
                Log.i(TAG, "没有可用摄像头设备");
                return false;
            }

            for (String id : ids) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
                Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    continue;
                }

                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing == mFacing) {
                    mCameraId = id;
                    mCameraCharacteristics = characteristics;
                    return true;
                }
            }

            if (!autoSelectWhenNoFacingCamera) {
                return false;
            }

            String id = ids[0];
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
            Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
            if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                return false;
            }

            mCameraId = id;
            mCameraCharacteristics = characteristics;
            mFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
            return true;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return false;
    }

    @SuppressLint("MissingPermission")
    @Override
    public void openCamera() {
        Log.i(TAG, "openCamera: mCameraId=" + mCameraId);
        try {
            mCameraManager.openCamera(mCameraId, mOpenCameraCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean isCameraOpened() {
        return mCamera != null;
    }

    private void startCaptureSession() {
        if (!isCameraOpened() || !mPreview.isReady()) {
            Log.i(TAG, "startCaptureSession: false");
            return;
        }
        Log.i(TAG, "startCaptureSession: true");
        try {
            Size size = chooseOptimalSize();
            Log.i(TAG, "choose Optimal Size: " + size.getWidth() + "-" + size.getHeight());
            mPreview.setBufferSize(size.getWidth(), size.getHeight());
            Surface surface = mPreview.getSurface();
            mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);


            Surface imageReaderSurface = createImageReaderSurface(size);
            mPreviewRequestBuilder.addTarget(imageReaderSurface);
            mImageReaderSurface = imageReaderSurface;


            mCamera.createCaptureSession(Arrays.asList(surface, mImageReaderSurface), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    Log.i(TAG, "createCaptureSession onConfigured: ");
                    if (mCamera == null) {
                        return;
                    }
                    mCaptureSession = session;
                    setPreviewRequest(true, true);
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    Log.i(TAG, "createCaptureSession onConfigureFailed: Failed to configure capture session.");
                }

                @Override
                public void onClosed(@NonNull CameraCaptureSession session) {
                    Log.i(TAG, "createCaptureSession onClosed: ");
                    if (mCaptureSession != null && mCaptureSession.equals(session)) {
                        mCaptureSession = null;
                    }
                }
            }, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Chooses the optimal preview size
     */
    private Size chooseOptimalSize() {
        StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if (map == null) {
            throw new IllegalStateException("Failed to get configuration map: " + mCameraId);
        }

        Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
        List<SmartSize> sizeList = new ArrayList<>();
        for (Size size : sizes){
            sizeList.add(new SmartSize(size));
        }

        Collections.sort(sizeList, (o1, o2) -> o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());

        SmartSize viewSize = new SmartSize(mPreview.getWidth(), mPreview.getHeight());

        for (SmartSize size : sizeList){
            if (size.getBig() >= viewSize.getBig() && size.getSmall() >= viewSize.getSmall()){
                return size.getSize();
            }
        }
        return sizeList.get(sizeList.size() - 1).getSize();
    }


    @Override
    public void setFacing(int facing) {
        super.setFacing(facing);
        if (isCameraOpened()) {
            stop();
            start();
        }
    }

    @Override
    public void setFlash(int flash) {
        super.setFlash(flash);
        setPreviewRequest(false, true);
    }

    @Override
    public void setAutoFocus(boolean autoFocus) {
        super.setAutoFocus(autoFocus);
        setPreviewRequest(true, false);
    }

    private void setPreviewRequest(boolean updateAutoFocus, boolean updateFlash) {
        if (mPreviewRequestBuilder != null) {
            if (updateAutoFocus) {
                updateAutoFocusSetting();
            }

            if (updateFlash) {
                updateFlashSetting();
            }

            if (mCaptureSession != null) {
                try {
                    Log.i(TAG, "setPreviewRequest: ");
                    mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Updates the internal state of auto-focus to {@link #mAutoFocus}.
     */
    private void updateAutoFocusSetting() {
        if (mAutoFocus) {
            int[] modes = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
            // Auto focus is not supported
            if (modes == null || modes.length == 0 || (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) {
                mAutoFocus = false;
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
            } else {
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            }
        } else {
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
        }
    }

    /**
     * Updates the internal state of flash to {@link #mFlash}.
     */
    private void updateFlashSetting() {
        switch (mFlash) {
            case FLASH_OFF:
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
                break;
            case FLASH_ON:
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
                break;
            case FLASH_TORCH:
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
                break;
            case FLASH_AUTO:
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
                break;
            case FLASH_RED_EYE:
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
                break;
        }
    }

    private Surface createImageReaderSurface(Size size){
        ImageReader imageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.YUV_420_888, 2);
        return imageReader.getSurface();
    }


}
